//! Module resolver for NRC language (ES6-style imports)

use crate::ast::*;
use crate::error::{CompilerError, Result};
use crate::parser::Parser;
use crate::project_config::ProjectConfig;
use std::collections::HashMap;
use std::fs;
use std::path::PathBuf;

/// Module resolver for handling import statements
pub struct ModuleResolver {
    /// Resolved modules cache
    modules: HashMap<String, Module>,
    /// Base directory for resolving relative imports
    base_dir: PathBuf,
    /// Project configuration
    project_config: ProjectConfig,
    /// Project configuration directory
    config_dir: PathBuf,
}

/// A resolved module
pub struct Module {
    /// Module path
    pub path: String,
    /// Module declarations
    pub declarations: Vec<Declaration>,
    /// Exported symbols
    pub exports: HashMap<String, ExportInfo>,
}

/// Information about an exported symbol
#[derive(Debug, Clone)]
pub struct ExportInfo {
    /// Symbol name
    pub name: String,
    /// Symbol type
    pub symbol_type: ExportType,
    /// Original declaration
    pub declaration: Declaration,
}

/// Type of exported symbol
#[derive(Debug, Clone)]
pub enum ExportType {
    /// Function export
    Function,
    /// Variable export
    Variable,
    /// Constant export
    Constant,
    /// Type export
    Type,
    /// Struct export
    Struct,
    /// Trait export
    Trait,
}

impl ModuleResolver {
    /// Create a new module resolver
    pub fn new(base_dir: PathBuf) -> Result<Self> {
        // Try to load project config
        let (project_config, config_dir) = match ProjectConfig::load_from_dir(&base_dir) {
            Ok((config, dir)) => (config, dir),
            Err(_) => {
                // If no config found, use default
                let config = ProjectConfig::create_default(&base_dir);
                (config, base_dir.clone())
            }
        };

        Ok(Self {
            modules: HashMap::new(),
            base_dir,
            project_config,
            config_dir,
        })
    }

    /// Resolve an import declaration
    pub fn resolve_import(&mut self, import: &ImportDecl) -> Result<()> {
        let module_path = self.resolve_module_path(&import.path)?;

        // Load and parse the module if not already loaded
        if !self.modules.contains_key(&module_path) {
            let module = self.load_module(&module_path)?;
            self.modules.insert(module_path.clone(), module);
        }

        Ok(())
    }

    /// Resolve module path from import path string
    ///
    /// Supports:
    /// - Relative paths: "./foo.nr", "../bar.nr"
    /// - Path aliases: "@project/foo.nr", "@std/fmt.nr"
    /// - Absolute paths: "/usr/local/lib/foo.nr"
    pub fn resolve_module_path(&self, import_path: &str) -> Result<String> {
        let path = import_path.trim();

        // Handle path aliases (@project, @std, @lib, etc.)
        if path.starts_with('@') {
            return self.resolve_aliased_path(path);
        }

        // Handle relative paths (./foo, ../bar)
        if path.starts_with("./") || path.starts_with("../") {
            let full_path = self.base_dir.join(path);
            return self.canonicalize_path(full_path);
        }

        // Handle absolute paths
        if path.starts_with('/') {
            let full_path = PathBuf::from(path);
            return self.canonicalize_path(full_path);
        }

        // If none of the above, treat as project-relative
        let full_path = self.base_dir.join(path);
        self.canonicalize_path(full_path)
    }

    /// Resolve path with alias (@project, @std, etc.)
    fn resolve_aliased_path(&self, path: &str) -> Result<String> {
        // Extract alias (e.g., "@project" from "@project/foo/bar.nr")
        let parts: Vec<&str> = path.splitn(2, '/').collect();
        if parts.is_empty() {
            return Err(CompilerError::internal(&format!(
                "Invalid aliased path: '{}'",
                path
            )));
        }

        let alias = parts[0];
        let rest = if parts.len() > 1 { parts[1] } else { "" };

        // Resolve the alias to actual path
        if let Some(alias_path) = self.project_config.resolve_alias(alias, &self.config_dir) {
            let full_path = alias_path.join(rest);
            self.canonicalize_path(full_path)
        } else {
            // Handle built-in aliases if not in config
            match alias {
                "@std" => self.resolve_std_path(rest),
                _ => Err(CompilerError::internal(&format!(
                    "Unknown path alias: '{}'. Add it to nrc.json paths configuration.",
                    alias
                ))),
            }
        }
    }

    /// Resolve @std path
    fn resolve_std_path(&self, rest: &str) -> Result<String> {
        let std_dir = Self::find_std_directory()?;
        let full_path = std_dir.join(rest);
        self.canonicalize_path(full_path)
    }

    /// Canonicalize path and return as string
    fn canonicalize_path(&self, path: PathBuf) -> Result<String> {
        // Check if file exists
        if !path.exists() {
            return Err(CompilerError::internal(&format!(
                "Module file not found: {}",
                path.display()
            )));
        }

        // Canonicalize to absolute path
        let canonical = path.canonicalize().map_err(|e| {
            CompilerError::internal(&format!(
                "Failed to canonicalize path '{}': {}",
                path.display(),
                e
            ))
        })?;

        Ok(canonical.to_string_lossy().to_string())
    }

    /// Load and parse a module
    fn load_module(&self, module_path: &str) -> Result<Module> {
        // Read the module file
        let content = fs::read_to_string(module_path).map_err(|e| {
            CompilerError::internal(&format!("Failed to read module '{}': {}", module_path, e))
        })?;

        // Parse the module
        let mut parser = Parser::new(content, Some(module_path.to_string()));
        let program = parser.parse_program().map_err(|e| {
            CompilerError::internal(&format!("Failed to parse module '{}': {}", module_path, e))
        })?;

        // Extract exports from the module
        let exports = self.extract_exports(&program.declarations)?;

        Ok(Module {
            path: module_path.to_string(),
            declarations: program.declarations,
            exports,
        })
    }

    /// Extract exported symbols from module declarations
    fn extract_exports(&self, declarations: &[Declaration]) -> Result<HashMap<String, ExportInfo>> {
        let mut exports = HashMap::new();

        for decl in declarations {
            match decl {
                Declaration::Function(func) => {
                    // Check if function is exported (Python-style: no underscore prefix)
                    if self.is_exported(decl) {
                        exports.insert(
                            func.name.clone(),
                            ExportInfo {
                                name: func.name.clone(),
                                symbol_type: ExportType::Function,
                                declaration: decl.clone(),
                            },
                        );
                    }
                }
                Declaration::Variable(var) => {
                    if self.is_exported(decl) {
                        exports.insert(
                            var.name.clone(),
                            ExportInfo {
                                name: var.name.clone(),
                                symbol_type: ExportType::Variable,
                                declaration: decl.clone(),
                            },
                        );
                    }
                }
                Declaration::Constant(const_) => {
                    if self.is_exported(decl) {
                        exports.insert(
                            const_.name.clone(),
                            ExportInfo {
                                name: const_.name.clone(),
                                symbol_type: ExportType::Constant,
                                declaration: decl.clone(),
                            },
                        );
                    }
                }
                Declaration::Type(type_) => {
                    if self.is_exported(decl) {
                        exports.insert(
                            type_.name.clone(),
                            ExportInfo {
                                name: type_.name.clone(),
                                symbol_type: ExportType::Type,
                                declaration: decl.clone(),
                            },
                        );
                    }
                }
                Declaration::Struct(struct_) => {
                    if self.is_exported(decl) {
                        exports.insert(
                            struct_.name.clone(),
                            ExportInfo {
                                name: struct_.name.clone(),
                                symbol_type: ExportType::Struct,
                                declaration: decl.clone(),
                            },
                        );
                    }
                }
                Declaration::Trait(trait_) => {
                    if self.is_exported(decl) {
                        exports.insert(
                            trait_.name.clone(),
                            ExportInfo {
                                name: trait_.name.clone(),
                                symbol_type: ExportType::Trait,
                                declaration: decl.clone(),
                            },
                        );
                    }
                }
                _ => {} // Ignore other declaration types
            }
        }

        Ok(exports)
    }

    /// Check if a declaration is exported (Python-style visibility)
    /// Default is public (exported), underscore prefix means private (not exported)
    fn is_exported(&self, decl: &Declaration) -> bool {
        let name = match decl {
            Declaration::Function(func) => &func.name,
            Declaration::Variable(var) => &var.name,
            Declaration::Constant(const_) => &const_.name,
            Declaration::Type(type_) => &type_.name,
            Declaration::Struct(struct_) => &struct_.name,
            Declaration::Enum(enum_) => &enum_.name,
            Declaration::Trait(trait_) => &trait_.name,
            Declaration::TraitImpl(_) => return false, // Trait implementations are not exported
            Declaration::Impl(_) => return false, // Impl blocks are not exported as standalone items
            Declaration::Import(_) => return false, // Import statements are not exported
        };

        // Python-style visibility: names starting with underscore are private
        !name.starts_with('_')
    }

    /// Get a module by path
    pub fn get_module(&self, path: &str) -> Option<&Module> {
        self.modules.get(path)
    }

    /// Get all loaded modules
    pub fn get_modules(&self) -> &HashMap<String, Module> {
        &self.modules
    }

    /// Find the std directory relative to the compiler executable
    fn find_std_directory() -> Result<PathBuf> {
        // Try to find the std directory relative to the current executable
        let exe_path = std::env::current_exe().map_err(|e| {
            CompilerError::internal(&format!("Failed to get current executable path: {}", e))
        })?;

        // Get the directory containing the executable
        let exe_dir = exe_path
            .parent()
            .ok_or_else(|| CompilerError::internal("Failed to get executable directory"))?;

        // Look for std directory in several possible locations:
        let possible_paths = vec![
            exe_dir.join("std"),
            exe_dir.join("../std"),
            exe_dir.join("../../std"),
            exe_dir.join("../../../std"),
        ];

        for path in possible_paths {
            if path.exists() && path.is_dir() {
                return Ok(path.canonicalize().unwrap_or(path));
            }
        }

        // If not found, try relative to current working directory
        let cwd_std = std::env::current_dir()
            .unwrap_or_else(|_| PathBuf::from("."))
            .join("std");

        if cwd_std.exists() && cwd_std.is_dir() {
            return Ok(cwd_std.canonicalize().unwrap_or(cwd_std));
        }

        Err(CompilerError::internal("Could not find std directory. Please ensure the std directory exists relative to the compiler executable or in the current working directory."))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::ast::types::BasicType;
    use crate::error::Location;

    #[test]
    fn test_python_style_visibility_public() {
        let resolver = ModuleResolver::new(PathBuf::from(".")).unwrap();
        let location = Location::new(1, 1, 0);

        // Test public function (no underscore prefix)
        let func = FunctionDecl {
            name: "public_func".to_string(),
            generics: vec![],
            parameters: vec![],
            return_type: Some(Type::Basic(BasicType::Int32)),
            body: None,
            attributes: vec![],
            location,
        };
        let decl = Declaration::Function(func);
        assert!(
            resolver.is_exported(&decl),
            "Public function should be exported"
        );

        // Test public variable
        let var = VariableDecl {
            name: "public_var".to_string(),
            var_type: Some(Type::Basic(BasicType::Int32)),
            initializer: None,
            mutable: true,
            location,
        };
        let decl = Declaration::Variable(var);
        assert!(
            resolver.is_exported(&decl),
            "Public variable should be exported"
        );

        // Test public constant
        let const_ = ConstantDecl {
            name: "PUBLIC_CONST".to_string(),
            const_type: Some(Type::Basic(BasicType::Int32)),
            value: Expression::Literal(Literal::Integer(100)),
            location,
        };
        let decl = Declaration::Constant(const_);
        assert!(
            resolver.is_exported(&decl),
            "Public constant should be exported"
        );

        // Test public struct
        let struct_ = StructDecl {
            name: "PublicStruct".to_string(),
            generics: vec![],
            fields: vec![],
            methods: vec![],
            location,
        };
        let decl = Declaration::Struct(struct_);
        assert!(
            resolver.is_exported(&decl),
            "Public struct should be exported"
        );
    }

    #[test]
    fn test_python_style_visibility_private() {
        let resolver = ModuleResolver::new(PathBuf::from(".")).unwrap();
        let location = Location::new(1, 1, 0);

        // Test private function (underscore prefix)
        let func = FunctionDecl {
            name: "_private_func".to_string(),
            generics: vec![],
            parameters: vec![],
            return_type: Some(Type::Basic(BasicType::Int32)),
            body: None,
            attributes: vec![],
            location,
        };
        let decl = Declaration::Function(func);
        assert!(
            !resolver.is_exported(&decl),
            "Private function should not be exported"
        );

        // Test private variable
        let var = VariableDecl {
            name: "_private_var".to_string(),
            var_type: Some(Type::Basic(BasicType::Int32)),
            initializer: None,
            mutable: true,
            location,
        };
        let decl = Declaration::Variable(var);
        assert!(
            !resolver.is_exported(&decl),
            "Private variable should not be exported"
        );

        // Test private constant
        let const_ = ConstantDecl {
            name: "_PRIVATE_CONST".to_string(),
            const_type: Some(Type::Basic(BasicType::Int32)),
            value: Expression::Literal(Literal::Integer(100)),
            location,
        };
        let decl = Declaration::Constant(const_);
        assert!(
            !resolver.is_exported(&decl),
            "Private constant should not be exported"
        );

        // Test private struct
        let struct_ = StructDecl {
            name: "_PrivateStruct".to_string(),
            generics: vec![],
            fields: vec![],
            methods: vec![],
            location,
        };
        let decl = Declaration::Struct(struct_);
        assert!(
            !resolver.is_exported(&decl),
            "Private struct should not be exported"
        );
    }

    #[test]
    fn test_impl_and_import_not_exported() {
        let resolver = ModuleResolver::new(PathBuf::from(".")).unwrap();
        let location = Location::new(1, 1, 0);

        // Test impl block (should never be exported as standalone)
        let impl_ = ImplDecl {
            type_name: "Foo".to_string(),
            generics: vec![],
            methods: vec![],
            location,
        };
        let decl = Declaration::Impl(impl_);
        assert!(
            !resolver.is_exported(&decl),
            "Impl blocks should not be exported"
        );

        // Test import statement (should never be exported)
        let import = ImportDecl {
            path: "./foo.nr".to_string(),
            default_import: None,
            named_imports: vec![],
            namespace_import: None,
            location,
        };
        let decl = Declaration::Import(import);
        assert!(
            !resolver.is_exported(&decl),
            "Import statements should not be exported"
        );
    }
}
